﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IRC.NET.Net;
using System.Diagnostics;
using IRC.NET.Commands;
using IRC.NET.Users;
using System.Text.RegularExpressions;

namespace IRC.NET
{
    public enum ServerMessageType
    {
        Notice,
        AuthNotice,
        Unknown,
        Ping
    }

    public delegate void MessageEventHandler(object sender, IrcServerMessageEventArgs args);
    public delegate void ServerEventHandler(object sender, IrcServerEventArgs args);

    public class IrcServer
    {
        private IrcServerConnection _connection;
        private List<TwoWayCommandBase> _pendingCommands;
        private List<Channel> _joinedChannels;

        public IList<Channel> Channels
        {
            get { return (this._joinedChannels); }
        }

        public ClientUser User
        {
            get;
            set;
        }

        public bool IsRegistered
        {
            get;
            set;
        }

        public string MOTD
        {
            get;
            set;
        }

        public string Host {
            get;
            set;
        }

        #region Events
        public event MessageEventHandler UnknownMessageRecieved;
        public event MessageEventHandler PingRecieved;
        public event MessageEventHandler NoticeRecieved;
        public event MessageEventHandler AuthNoticeRecieved;

        public event ServerEventHandler ConnectionOpened;
        public event ServerEventHandler Authenticated;

        public event ChannelEventHandler JoinedChannel;
        #endregion

        #region ctors
        public IrcServer(string connectionString, ClientUser user)
        {
            this._pendingCommands = new List<TwoWayCommandBase>();
            this._joinedChannels = new List<Channel>();
            this.User = user;
            this.IsRegistered = false;

            this._connection = new IrcServerConnection(connectionString);
            this._connection.MessageRecieved += new MessageRecievedEventHandler(_OnServerMessageRecieved);
        }
        #endregion

        public void Open()
        {
            try
            {
                this._connection.OpenConnection();
                this.OnConnectionOpened(new IrcServerEventArgs(this));

                // We need to send a NICK command.
                this._connection.SendCommand(new NickCommand(this.User));
                this._connection.SendCommand(new UserCommand(this.User));
            }
            catch (Exception e)
            {
                throw new ServerConnectionException("Unable to connect to server.", e);
            }
        }

        public void Close()
        {
            if (this.IsRegistered)
            {
                foreach (Channel channel in this._joinedChannels)
                {
                    this._connection.SendCommand(new PartCommand(channel, "This is a test user. Please ignore my crappy spam :("));
                }
            }
            this._connection.Close();
        }

        public Channel JoinChannel(string channelName)
        {
            JoinCommand command = new JoinCommand(this, channelName);
            command.JoinComplete += new ChannelEventHandler(_OnChannelJoined);
            this._pendingCommands.Add(command);

            this._connection.SendCommand(command);
            
            return (null);
        }

        public void LeaveChannel(string channelName)
        {
            Channel channel = null;
            foreach (Channel c in this._joinedChannels)
            {
                if (string.Equals(channel.Name.ToUpper(), channelName.ToUpper()))
                {
                    channel = c;
                    break;
                }
            }

            if (channel != null)
            {
                this.LeaveChannel(channel);
            }
        }

        public void LeaveChannel(Channel channel)
        {
            PartCommand cmd = new PartCommand(channel, "I'm leaving you -- good bye!");
            this._connection.SendCommand(cmd);
        }

        internal void EmitNotice(string message) 
        {
            IrcServerMessageEventArgs args = new IrcServerMessageEventArgs(this, ServerMessageType.Notice, message);
            this.OnNoticeRecieved(args);
        }

        private void _OnServerMessageRecieved(object sender, MessageRecievedEventArgs args) 
        {
            if (!(sender is IrcServerConnection))
            {
                // We need to get outta here.
                Debug.WriteLine("Event sender was not a server. Object: " + sender.ToString());
                return;
            }

            string message = args.Message;
            if (this._HandleKnownCommand(message))
            {
                return;
            }

            // Otherwise we have a command that we need to deploy here.
            ServerReply serverMessage = ServerReply.Parse(message);
            if (this._HandleServerCommands(serverMessage))
            {
                return;
            }

            // Lets handle server-based events, too.

			// We want to make sure we throw an unknown command here.
			this.OnUnknownMessageRecieved(new IrcServerMessageEventArgs(this, ServerMessageType.Unknown, message));

            // First lets see if there is a pending command that can consume this response.
            if(this._pendingCommands.Count > 0) 
            {
                TwoWayCommandBase consumingCommand = null;
                foreach (TwoWayCommandBase command in this._pendingCommands)
                {
                    if (command.IsResponse(serverMessage))
                    {
                        bool remove = command.ProcessResponse(serverMessage);

                        // If that method returns true we need to remove it from the list of pending commands.
                        if (remove)
                        {
                            consumingCommand = command;
                        }

                        break;
                    }
                }

                if (consumingCommand != null)
                {
                    this._pendingCommands.Remove(consumingCommand);
                    return; // We're done with thie command.
                }
            }
        }

        private bool _HandleServerCommands(ServerReply message)
        {
            switch (message.MessageCode)
            {
                case ServerReply.RPL_MOTDSTART:
                    return (true);
                case ServerReply.RPL_MOTD:
                    this.MOTD += message.Message;
                    return (true);
                case ServerReply.RPL_YOURHOST:
                    string pattern = @"Your host is (?<host>\S+) ";
                    if(Regex.IsMatch(message.Message, pattern)) {
                        Match match = Regex.Match(message.Message, pattern);
                        this.Host = match.Groups["host"].Value;
                    }
                    return (true);
                case ServerReply.RPL_BOUND:
                    this.OnAuthenticated(new IrcServerEventArgs(this));
                    return (true);
                // TODO: Implement these.
                case ServerReply.RPL_LUSERCLIENT:
                case ServerReply.RPL_LUSEROP:
                case ServerReply.RPL_LUSERUNKNOWN:
                case ServerReply.RPL_LUSERCHANNELS:
                case ServerReply.RPL_LUSERME:
                case ServerReply.RPL_ADMINME:
                case ServerReply.RPL_ENDOFMOTD:
                    return (true);
            }

            return (false);
        }

        private bool _HandleKnownCommand(string message)
        {
            if(message.ToUpper().StartsWith("NOTICE AUTH"))
            {
                // Fire the NoticeAuthMessageRecieved event.
                IrcServerMessageEventArgs serverEventArgs = new IrcServerMessageEventArgs(this, ServerMessageType.AuthNotice, message);
                this.OnNoticeRecieved(serverEventArgs);
                return (true);
            }
            else if (message.ToUpper().StartsWith("NOTICE"))
            {
                IrcServerMessageEventArgs serverEventArgs = new IrcServerMessageEventArgs(this, ServerMessageType.Notice, message);
                this.OnNoticeRecieved(serverEventArgs);
                return (true);
            }
            else if (message.ToUpper().StartsWith("PING"))
            {
                IrcServerMessageEventArgs serverEventArgs = new IrcServerMessageEventArgs(this, ServerMessageType.Ping, message);
                this.OnPingRecieved(serverEventArgs);
                return (true);
            }

            return (false);
        }

        private string _CleanMessage(string message)
        {
            int idx = message.IndexOf(this.User.Nickname);
            if (idx < 0)
            {
                return (message); // Already cleaned.
            }
            else
            {
                idx += this.User.Nickname.Length;
            }

            string clean = message.Substring(idx, message.Length - idx);
            return (clean.Trim());
        }

        private void _OnChannelJoined(object sender, ChannelEventArgs args)
        {
            // Add this to the list of joined channels.
            if (!args.Server.Equals(this))
            {
                Debug.WriteLine("Somehow server reporting channel join event?");
                return;
            }

            this._joinedChannels.Add(args.Channel);

            if (this.JoinedChannel != null)
            {
                this.JoinedChannel(this, args);
            }
        }
        
        protected void OnUnknownMessageRecieved(IrcServerMessageEventArgs args)
        {
            if (this.UnknownMessageRecieved != null)
            {
                this.UnknownMessageRecieved(this, args);
            }
        }

        protected void OnConnectionOpened(IrcServerEventArgs args)
        {
            if (this.ConnectionOpened != null)
            {
                this.ConnectionOpened(this, args);
            }
        }

        protected void OnAuthenticated(IrcServerEventArgs args)
        {
            this.IsRegistered = true;

            if (this.Authenticated != null)
            {
                this.Authenticated(this, args);
            }
        }

        protected void OnNoticeRecieved(IrcServerMessageEventArgs args)
        {
            if (args.MessageType == ServerMessageType.AuthNotice)
            {
                if (this.AuthNoticeRecieved != null)
                {
                    this.AuthNoticeRecieved(this, args);
                }
            }
            else if(args.MessageType == ServerMessageType.Notice)
            {
                if (this.NoticeRecieved != null)
                {
                    this.NoticeRecieved(this, args);
                }
            }
        }

        protected void OnPingRecieved(IrcServerMessageEventArgs args)
        {
            // TODO: Beef this shit up.
            if (string.IsNullOrEmpty(args.Message))
            {
                return;
            }

            int idx = args.Message.IndexOf(':')+1;
            string server = args.Message.Substring(idx, args.Message.Length - idx);
            this._connection.SendCommand(new PongCommand(server));

            if (this.PingRecieved != null)
            {
                this.PingRecieved(this, args);
            }
        }
    }
}
